package net.canang.codegen.task;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import net.canang.codegen.engine.domain.Domain;
import org.apache.commons.lang.StringUtils;
import org.apache.texen.Generator;
import org.apache.texen.ant.TexenTask;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;

import java.io.*;
import java.util.*;

/**
 * A base torque task that uses either a single XML schema
 * representing a data model, or a &lt;fileset&gt; of XML schemas.
 * We are making the assumption that an XML schema representing
 * a data model contains tables for a <strong>single</strong>
 * database.
 *
 * @author <a href="mailto:jvanzyl@zenplex.com">Jason van Zyl</a>
 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
 */
public class DataModelTask extends TexenTask {
    /**
     * Data models that we collect. One from each XML schema file.
     */
    protected List<Domain> domains = new ArrayList<Domain>();

    /**
     * Velocity context which exposes our objects in the templates.
     */
    protected Context context;

    /**
     * Return the data models that have been processed.
     *
     * @return List data models
     */
    public List getDomains() {
        return domains;
    }


    /**
     * Set up the initial context for generating the SQL from the XML schema.
     *
     * @return the context
     * @throws Exception
     */
    public Context initControlContext() throws Exception {

        context = new VelocityContext();

        // Place our set of data models into the context along
        // with the names of the databases as a convenience for now.
        context.put("domains", domains);
        return context;
    }

    /**
     * Change type of "now" to java.util.Date
     *
     * @see org.apache.texen.ant.TexenTask#populateInitialContext(org.apache.velocity.context.Context)
     */
    protected void populateInitialContext(Context context) throws Exception {
        super.populateInitialContext(context);
        context.put("now", new Date());
    }

    /**
     * This message fragment (telling users to consult the log or
     * invoke ant with the -debug flag) is appended to rethrown
     * exception messages.
     */
    private final static String ERR_MSG_FRAGMENT =
            ". For more information consult the velocity log, or invoke ant " +
                    "with the -debug flag.";

    /**
     * This method creates an VelocityEngine instance, parses
     * every template and creates the corresponding output.
     * <p/>
     * Unfortunately the TextenTask.execute() method makes
     * everything for us but we just want to set our own
     * VelocityTemplateLoader.
     * TODO: change once TEXEN-14 is resolved and out.
     *
     * @see org.apache.texen.ant.TexenTask#execute()
     */
    public void execute() throws BuildException {
        // Make sure the template path is set.
        if (templatePath == null && useClasspath == false) {
            throw new BuildException(
                    "The template path needs to be defined if you are not using "
                            + "the classpath for locating templates!");
        }

        // Make sure the control template is set.
        if (controlTemplate == null) {
            throw new BuildException(
                    "The control template needs to be defined!");
        }

        // Make sure the output directory is set.
        if (outputDirectory == null) {
            throw new BuildException(
                    "The output directory needs to be defined!");
        }

        // Make sure there is an output file.
        if (outputFile == null) {
            throw new BuildException("The output file needs to be defined!");
        }

        VelocityEngine ve = new VelocityEngine();

        try {

            ve.init();

            // Create the text generator.
            Generator generator = Generator.getInstance();
            generator.setVelocityEngine(ve);
            generator.setOutputPath(outputDirectory);
            generator.setInputEncoding(inputEncoding);
            generator.setOutputEncoding(outputEncoding);

            if (templatePath != null) {
                generator.setTemplatePath(templatePath);
            }

            // Make sure the output directory exists, if it doesn't
            // then create it.
            File file = new File(outputDirectory);
            if (!file.exists()) {
                file.mkdirs();
            }

            String path = outputDirectory + File.separator + outputFile;
            Writer writer = generator.getWriter(path, outputEncoding);

            // The generator and the output path should
            // be placed in the init context here and
            // not in the generator class itself.
            Context c = initControlContext();

            // Everything in the generator class should be
            // pulled out and placed in here. What the generator
            // class does can probably be added to the Velocity
            // class and the generator class can probably
            // be removed all together.
            populateInitialContext(c);

            // Feed all the options into the initial
            // control context so they are available
            // in the control/worker templates.
            if (contextProperties != null) {
                Iterator i = contextProperties.getKeys();

                while (i.hasNext()) {
                    String property = (String) i.next();
                    String value = contextProperties.getString(property);

                    // Now lets quickly check to see if what
                    // we have is numeric and try to put it
                    // into the context as an Integer.
                    try {
                        c.put(property, new Integer(value));
                    } catch (NumberFormatException nfe) {
                        // Now we will try to place the value into
                        // the context as a boolean value if it
                        // maps to a valid boolean value.
                        String booleanString = contextProperties
                                .testBoolean(value);

                        if (booleanString != null) {
                            c.put(property, new Boolean(booleanString));
                        } else {
                            // We are going to do something special
                            // for properties that have a "file.contents"
                            // suffix: for these properties will pull
                            // in the contents of the file and make
                            // them available in the context. So for
                            // a line like the following in a properties file:
                            //
                            // license.file.contents = license.txt
                            //
                            // We will pull in the contents of license.txt
                            // and make it available in the context as
                            // $license. This should make texen a little
                            // more flexible.
                            if (property.endsWith("file.contents")) {
                                // We need to turn the license file from
                                // relative to
                                // absolute, and let Ant help :)
                                value = org.apache.velocity.util.StringUtils
                                        .fileContentsToString(project
                                                .resolveFile(value)
                                                .getCanonicalPath());

                                property = property.substring(0, property
                                        .indexOf("file.contents") - 1);
                            }

                            c.put(property, value);
                        }
                    }
                }
            }

            writer.write(generator.parse(controlTemplate, c));
            writer.flush();
            writer.close();
            generator.shutdown();
            cleanup();
        }
        catch (BuildException e) {
            throw e;
        }
        catch (MethodInvocationException e) {
            throw new BuildException("Exception thrown by '"
                    + e.getReferenceName() + "." + e.getMethodName() + "'"
                    + ERR_MSG_FRAGMENT, e.getWrappedThrowable());
        }
        catch (ParseErrorException e) {
            throw new BuildException(
                    "Velocity syntax error" + ERR_MSG_FRAGMENT, e);
        }
        catch (ResourceNotFoundException e) {
            throw new BuildException(
                    "Resource not found" + ERR_MSG_FRAGMENT,
                    e);
        }
        catch (Exception e) {
            throw new BuildException(
                    "Generation failed" + ERR_MSG_FRAGMENT,
                    e);
        }
    }
}
